/**
	Pulsar engine. Additional include file.
	Memory manager (debug).

	Copyright (C) 2013-2014 Dmitry Dolgov

	This program is free software; you can redistribute it and/or modify it under the terms of the
	GNU General Public License as published by the Free Software Foundation; either version 2 of
	the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
	without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
	See the GNU General Public License for more details.
*/

namespace pl
{

//
// Forward declarations
//
template<pl_uint32 N> class String;
typedef String<MAX_PATH> Filepath;
class StreamWrite;

//
// Aux declarations
//
#if defined(PL_MEMORY_DEBUG)
#define _STUB_FUNCTION_RETURN(A)  extern A;
#define _STUB_FUNCTION_RETURN_FALSE(A)  extern A;
#elif defined(PL_MEMORY_RELEASE) || defined(PL_MEMORY_FINAL) || defined(PL_MEMORY_NONE)
#define _STUB_FUNCTION_RETURN(A)  inline A { return; }
#define _STUB_FUNCTION_RETURN_FALSE(A) inline A { return false; }
#else
#error "Wrong debug memory management type specified"
#endif // PL_MEMORY_DEBUG



/// Memory damage options
enum MemoryError
{
	/// No error (0)
	MEMORY_ERROR_NONE,
	/// Memory header corruption (possible negate array index) (1)
	MEMORY_ERROR_HEADER,
	/// Memory footer correption (possible too large array index) (2)
	MEMORY_ERROR_FOOTER,
	/// Trying to free already free block (3)
	MEMORY_ERROR_FREE_AGAIN,
	/// Call free(NULL) (4)
	MEMORY_ERROR_FREE_NULL,
	/// Call malloc(0) or calloc(0) (5)
	MEMORY_ERROR_MALLOC_ZERO,
	/// Allocation overflow (6)
	MEMORY_ERROR_MALLOC_OVERFLOW,
	/// Pointer in PL_DELETE was allocated with different operator (7)
	MEMORY_ERROR_MIX_DELETE,
	/// Pointer in PL_DELETE[] was allocated with different operator (8)
	MEMORY_ERROR_MIX_DELETE_ARRAY,
	/// Pointer in free() was allocated with different operator (9)
	MEMORY_ERROR_MIX_FREE,
	/// Pointer in free_aling() was allocated with different operator (10)
	MEMORY_ERROR_MIX_FREE_ALIGN,
	/// Pointer in malloc_static() was allocated with different operator (11)
	MEMORY_ERROR_MIX_STATIC,
	/// Pointer in malloc()/delete was allocated with different operator (12)
	MEMORY_ERROR_MIX_UNKNOWN,
	/// Allocate memory before generic initialization (13)
	MEMORY_ERROR_BEFORE_INIT,
	/// Allocation is disabled (14)
	MEMORY_ERROR_ALLOC_DISABLED,
	/// NULL-pointer (15)
	MEMORY_ERROR_NULL,
	/// Allocation index is reached (16)
	MEMORY_ERROR_INDEX_REACHED,
	/// Allocation address is reached (17)
	MEMORY_ERROR_ADDRESS_REACHED,
	/// Mix thread in allocation/deallocation (18)
	MEMORY_ERROR_MIX_THREAD,
	/// Out of memory
	MEMORY_ERROR_OUT,
	/// Another errors (19)
	MEMORY_ERROR_GENERAL
};

/// Type of allocations (see BlockInfo)
enum BlockType
{
	BLOCK_TYPE_MALLOC,       /// 0 malloc()/calloc()/realloc()/strdup()/free()
	BLOCK_TYPE_MALLOC_ALIGN, /// 1 malloc_align() or memalign()/free_align()
	BLOCK_TYPE_NEW,          /// 2 PL_NEW/PL_DELETE
	BLOCK_TYPE_NEW_ARRAY,    /// 3 PL_NEW[]/PL_DELETE[]
	BLOCK_TYPE_STATIC,       /// 4 malloc_static()/free_static()
	BLOCK_TYPE_EVENT,        /// 5 Memory event (reserved for memory monitor)
	BLOCK_TYPE_MAX
};

/// pl::memoryDebugWalk() structure
struct BlockInfo
{
	void* p;                   /// Pointer to user block
	void* pAllocated;          /// Pointer to system block (internally allocated)
	size_t size;               /// Size of block in bytes
	const pl_char* fileName;   /// Source file name where allocation occurs (may be NULL)
	pl_uint32 lineNumber;      /// Source line number where allocation occurs (may be 0)
	const pl_char* funcName;   /// Function name where allocation occurs (may be NULL)
	bool usageFlag;            /// true if used, false if free
	MemoryError blockStatus;   /// pl::MEMORY_ERROR_NONE or block error code
	BlockType allocType;       /// Allocation type
	pl_uint32 allocationIndex; /// Allocation order
	pl_uintptr threadId;       /// Allocation thread code
};

#if defined(PL_MEMORY_DEBUG)

// Internal allocations for PL_MEMORY_DEBUG
extern void* mallocDbg(size_t size, BlockType type, int align, const pl_char* file, pl_uint32 line, const pl_char* func);
extern void* callocDbg(size_t n, size_t size, const pl_char* file, pl_uint32 line, const pl_char* func);
extern void* reallocDbg(void* p, size_t size, const pl_char* file, pl_uint32 line, const pl_char* func);
extern void freeDbg(void* p, BlockType type, const pl_char* file, pl_uint32 line, const pl_char* func);
extern char* strdupDbg(const char* str, const pl_char* file, pl_uint32 line, const pl_char* func);
extern void* mallocStaticDbg(size_t size, const pl_char* file, pl_uint32 line, const pl_char* func);
extern void freeStaticDbg(void* p, const pl_char* file, pl_uint32 line, const pl_char* func);

extern pl::MemoryError memDebugCheckBlock(const void* p);

#elif defined(PL_MEMORY_RELEASE) || defined(PL_MEMORY_FINAL) || defined(PL_MEMORY_NONE)

inline pl::MemoryError memDebugCheckBlock(const void* p)
{
	return (p == NULL) ? MEMORY_ERROR_NULL : pl::MEMORY_ERROR_NONE;
}

#else
#error "Wrong debug memory management type specified"
#endif // PL_MEMORY_DEBUG

struct MemoryStat;

/// Dump all allocations to file
_STUB_FUNCTION_RETURN_FALSE(bool memDebugMemoryDump(const pl::Filepath& fileName, bool skipPLAllocations = false))
/// Dump all allocations to output stream
_STUB_FUNCTION_RETURN_FALSE(bool memDebugMemoryDump(pl::StreamWrite* s, bool skipPLAllocations = false))
/// Dump all allocations to log
_STUB_FUNCTION_RETURN(void memDebugMemoryLog(bool skipPLAllocations = false))
/// Dump all allocations to trace
_STUB_FUNCTION_RETURN(void memDebugMemoryTrace(bool skipPLAllocations = false))
/// Walk all allocations
_STUB_FUNCTION_RETURN_FALSE(bool memDebugMemoryWalk(pl::BlockInfo& info))
/// Summary memory status
_STUB_FUNCTION_RETURN_FALSE(bool memDebugMemoryStatus(MemoryStat &status, bool bCheckErrors))

/// Break into debugger when allocation occurs
_STUB_FUNCTION_RETURN(void memDebugSetAllocBreakIndex(pl_uint32 index))
/// Break into debugger when address in reached
_STUB_FUNCTION_RETURN(void memDebugSetAllocBreakAddress(const void* address))

/// Enable or disable allocations
_STUB_FUNCTION_RETURN(void memDebugEnableMallocs(bool fEnable))
/// Enable or disable mix allocations between threads
_STUB_FUNCTION_RETURN(void memDebugEnableThreadCheck(bool bEnable))

} // namespace pl

//
// End of file 'pulsar_memorydbg.h'
//
